/*
 * Copyright (c) 2011, Andrew Cole
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This file has been modified from the original Adobe PMD AS3Parser. It parses
 * tokens in the same way but populates transfer objects that are, in turn,
 * set on the TransferClass object that represents this class.
 *
 */

package net.andycole.xmiparser.parser;

import java.io.File;
import java.io.IOException;

import java.util.ArrayList;
import java.util.List;

import de.bokelberg.flex.parser.AS3Scanner.Token;
import com.adobe.ac.pmd.files.impl.FileUtils;

import com.adobe.ac.pmd.parser.Operators;
import com.adobe.ac.pmd.parser.KeyWords;
import com.adobe.ac.pmd.parser.NodeKind;
import com.adobe.ac.pmd.parser.exceptions.TokenException;
import com.adobe.ac.pmd.parser.exceptions.NullTokenException;
import com.adobe.ac.pmd.parser.exceptions.UnExpectedTokenException;
import com.adobe.ac.pmd.parser.exceptions.UnExpectedTokenException.Position;

import net.andycole.xmiparser.transfer.TransferClass;
import net.andycole.xmiparser.transfer.TransferOperation;
import net.andycole.xmiparser.transfer.TransferAttribute;
import net.andycole.xmiparser.transfer.TransferParameter;


/**
 * Parses Actionscript .as files (this includes code blocks for mxml files).
 * Currently attributes, operations and parameters are populated. Arrays are
 * handled as Array types, not as a multiplicity of the contained types.
 * Initial (default) values are not recorded.
 * Associations, Dependencies and Compositions are also not recorded correctly.
 * @author Andrew Cole
 */
public class ASFileParser extends AbstractASParser implements IASParser
{
   private static final String NEW_LINE = "\n";
   private String              fileName;

   private boolean             isInFor;
   private ScannerAccessor     scn;
   private Token               tok;
   private String              lastAnnotation;
   private String              lastMeta;

   public static final String  SINGLE_LINE_COMMENT    = "//";
   public static final String  MULTIPLE_LINES_COMMENT = "/*";
   public static final String  ASDOC_COMMENT          = "/**";
   
   public ASFileParser()
   {
      
   }

   @Override
   public TransferClass parse( final String filePath,
                               final String packageName) throws IOException, TokenException
   {
      return parse(filePath,
                   packageName,
                   FileUtils.readLines( new File( filePath ) ) );
   }

   @Override
   public TransferClass parse(final String filePath,
                              final String packageName,
                              final String[] lines ) throws TokenException
   {
      fileName = filePath;
      scn = new ScannerAccessor();
      scn.setLines( lines );
      _classInfo.setPackagePath(packageName);
      _packageName = packageName;
      
      nextToken();
      if ( tokIs( KeyWords.PACKAGE ) )
      {
         parsePackage();
      }
      parsePackageContent();

      return _classInfo;
   }

   /**
    * Compare the current token to the parameter
    *
    * @param text
    * @return true, if tok's text property equals the parameter
    */
   private boolean tokIs( final String text )
   {
      return tok.getText().equals( text );
   }
   private boolean tokIs( final Operators operator )
   {
      return tok.getText().compareTo( operator.toString() ) == 0;
   }
   private boolean tokIs( final KeyWords keyword )
   {
      return tok.getText().compareTo( keyword.toString() ) == 0;
   }
   private void consume( final KeyWords keyword ) throws TokenException
   {
      consume( keyword.toString() );
   }

   private void consume( final Operators operator ) throws TokenException
   {
      consume( operator.toString() );
   }
   /**
    * Compare the current token to the parameter. If it equals, get the next
    * token. If not, throw a runtime exception.
    *
    * @param text
    * @throws UnExpectedTokenException
    */
   private void consume( final String text ) throws TokenException
   {
      while ( tok.getText().startsWith( "//" ) )
      {
         nextToken();
      }

      if ( !tokIs( text ) )
      {
         throw new UnExpectedTokenException( tok.getText(),
                                             new Position( tok.getLine(),
                                                           tok.getColumn()),
                                             fileName, text );
      }
      nextToken();
   }

   private void parsePackage() throws TokenException
   {
      consume( KeyWords.PACKAGE );
      final StringBuffer nameBuffer = new StringBuffer();

      while ( !tokIs( Operators.LEFT_CURLY_BRACKET ) )
      {
         nameBuffer.append( tok.getText() );
         nextToken();
      }

      //name of the package is contained in name buffer.

      consume( Operators.LEFT_CURLY_BRACKET );
      parsePackageContent();
      consume( Operators.RIGHT_CURLY_BRACKET );
   }

   private void parsePackageContent() throws TokenException
   {
      final List< Token > modifier = new ArrayList< Token >();

      while ( !tokIs( Operators.RIGHT_CURLY_BRACKET )
            && !tokIs( KeyWords.EOF ) )
      {
         if ( tokIs( KeyWords.IMPORT ) )
         {
            parseImport();
         }
         else if ( tokIs( KeyWords.USE ) )
         {
            parseUse();
         }
         else if ( tokIs( Operators.LEFT_SQUARE_BRACKET ) )
         {
            parseMetaData();
         }
         else if ( tokIs( KeyWords.CLASS ) )
         {
//            parseClass( meta,
//                        modifier );
            _classInfo.setAnnotation(lastAnnotation);
            lastAnnotation = null;
            parseClass();

            modifier.clear();
//            meta.clear();
         }
         else if ( tokIs( KeyWords.INTERFACE ) )
         {
//            parseInterface( meta,
//                            modifier );
            _classInfo.setAnnotation(lastAnnotation);
            lastAnnotation = null;
            parseInterface();
            
            modifier.clear();
         }
         else if ( tokIs( KeyWords.FUNCTION ) )
         {
            parseClassFunctions("private", false, false);
//            accessLevel = "private";
//            isStatic = false;
//            isConst = false;
            lastAnnotation = null;
         }
         else if ( tok.getText().startsWith( ASDOC_COMMENT ) )
         {
            lastAnnotation = tok.getText();
            nextToken();
         }
         else if ( tok.getText().startsWith( MULTIPLE_LINES_COMMENT ) )
         {
            lastAnnotation = null;
            nextToken();
         }

         else
         {
            modifier.add( tok );
            nextTokenIgnoringDocumentation();
         }
      }
   }

   private void parseImport() throws TokenException
   {
      consume( KeyWords.IMPORT );
      String importName = parseImportName();
      String importClassName;
      if (importName.lastIndexOf('.') > -1)
         importClassName = importName.substring(importName.lastIndexOf('.')+1);
      else
         importClassName = importName;
      _importTypeTable.put(importClassName, importName);
//      _classInfo.addDependency(importName); or
      _classInfo.addDependency(getQualifiedType(importName));
      skip( Operators.SEMI_COLUMN );
   }

   private String parseImportName() throws TokenException
   {
      final StringBuffer result = new StringBuffer();

      result.append( tok.getText() );
      nextToken();
      while ( tokIs( Operators.DOT ) )
      {
         result.append( Operators.DOT );
         nextToken(); // .
         result.append( tok.getText() );
         nextToken(); // part of name
      }
      return result.toString();
   }

   private void parseUse() throws TokenException
   {
      consume( KeyWords.USE );
      consume( KeyWords.NAMESPACE );
      parseNamespaceName();
      skip( Operators.SEMI_COLUMN );
   }

   private String parseNamespaceName() throws TokenException
   {
      final String name = tok.getText();
      nextToken(); // simple name for now
      return name;
   }

   private String parseMetaData() throws TokenException
   {
      StringBuilder buffer = new StringBuilder();
      consume( Operators.LEFT_SQUARE_BRACKET );
      while ( !tokIs( Operators.RIGHT_SQUARE_BRACKET ) )
      {
         buffer.append(tok.getText());
//         if ( buffer.length() > 0 )
//         {
//            buffer.append( ' ' );
//         }
//         buffer.append( tok.getText() );
         nextToken();
      }
      String metadata = buffer.toString();
      skip( Operators.RIGHT_SQUARE_BRACKET );
      return metadata;
   }

   private void parseClass() throws TokenException
   {
      consume( KeyWords.CLASS );

      String classname = tok.getText();
      //_classInfo.setPackagePath(fileName);
      _classInfo.setClassName(classname);
      nextToken(); // name
//      convertMeta( meta );
//      convertModifiers( modifier );
      do
      {
         if ( tokIs( KeyWords.EXTENDS ) )
         {
            nextToken(); // extends
            String className = parseQualifiedName();
            _classInfo.setExtends(getQualifiedType(className));
         }
         else if ( tokIs( KeyWords.IMPLEMENTS ) )
         {
            parseImplementsList();
         }
      }
      while ( !tokIs( Operators.LEFT_CURLY_BRACKET ) );
      consume( Operators.LEFT_CURLY_BRACKET );
      parseClassContent();
      consume( Operators.RIGHT_CURLY_BRACKET );
   }

   private void parseImplementsList() throws TokenException
   {
      consume( KeyWords.IMPLEMENTS );

      _classInfo.addImplement(getQualifiedType(parseQualifiedName()));
      while ( tokIs( Operators.COMMA ) )
      {
         nextToken();
         _classInfo.addImplement(getQualifiedType(parseQualifiedName()));
      }
   }

   final void parseClassContent() throws TokenException
   {
      String accessLevel = "private";
      Boolean isStatic = false;
      Boolean isConst = false;

      while ( !tokIs( Operators.RIGHT_CURLY_BRACKET ) )
      {
         if ( tokIs( Operators.LEFT_CURLY_BRACKET ) )
         {
            parseBlock();
            lastAnnotation = null;
         }
         if ( tokIs( Operators.LEFT_SQUARE_BRACKET ) )
         {
            lastMeta = parseMetaData();
         }
         else if ( tokIs( KeyWords.VAR ) )
         {
            parseClassField(accessLevel, isStatic);
            isStatic = false;
            isConst = false;
            lastAnnotation = null;
            lastMeta = null;
         }
         else if ( tokIs( KeyWords.CONST ) )
         {
            //isConst = true;
            parseClassConstant(accessLevel, isStatic);
            isStatic = false;
            isConst = false;
            lastAnnotation = null;
            lastMeta = null;
         }
         else if ( tokIs( KeyWords.IMPORT ) )
         {
            parseImport();
         }
         else if ( tokIs( KeyWords.FUNCTION ) )
         {
            parseClassFunctions(accessLevel, isStatic, isConst);
            lastAnnotation = null;
            accessLevel = "private";
            isStatic = false;
            isConst = false;
            lastMeta = null;
         }
         else if ( tokIs( KeyWords.PRIVATE )   ||
                   tokIs( KeyWords.PROTECTED ) ||
                   tokIs( KeyWords.PUBLIC ) )
         {
            accessLevel = tok.getText();
            nextToken();
         }
         else if ( tokIs( KeyWords.STATIC ) )
         {
            isStatic = true;
            nextToken();
         }
//         else if ( tokIs( KeyWords.CONST ) )
//         {
//            isConst = true;
//            nextToken();
//         }
         //FINAL
         else
         {
            tryToParseCommentNode();
            //nextToken();
         }
      }
   }

   private void parseClassField(String accessLevel,
                                boolean isStatic) throws TokenException
   {
      parseVarList(true, accessLevel, isStatic);
      if ( tokIs( Operators.SEMI_COLUMN ) )
      {
         nextToken();
      }
   }

   private void parseVarList(boolean isClassLevel,
                             String  accessLevel,
                             boolean isStatic) throws TokenException
   {
      consume( KeyWords.VAR );
//      result.addChild( convertMeta( meta ) );
//      result.addChild( convertModifiers( modifiers ) );
      collectVarListContent(isClassLevel, accessLevel, false, isStatic);
   }

   private String extractTypeFromMeta(String metadata)
   {
      // Array type info is in the form ArrayElementType=("type")
      String retType = null;
      if (metadata != null &&
          metadata.contains("ArrayElementType"))
      {
         retType = metadata.replace("ArrayElementType(\"", "");
         retType = retType.replace("\")", "");
      }

      return retType;
   }

   private void collectVarListContent(boolean isClassLevel,
                                      String  accessLevel,
                                      boolean isConst,
                                      boolean isStatic) throws TokenException
   {
      TransferAttribute variable = parseNameTypeInit();
      variable.setConst(isConst);
      variable.setAccessLevel(accessLevel);
      variable.setStatic(isStatic);
      variable.setAnnotation(lastAnnotation);

      if (isClassLevel)
         _classInfo.addAttribute(variable);
      while ( tokIs( Operators.COMMA ) )
      {
         nextToken();
         variable = parseNameTypeInit();
         variable.setConst(isConst);
         variable.setAccessLevel(accessLevel);
         variable.setStatic(isStatic);
         if (isClassLevel)
            _classInfo.addAttribute(variable);
      }
   }

   private void parseClassFunctions(String  accessLevel,
                                    boolean isStatic,
                                    boolean isConst) throws TokenException
   {
      parseFunction(true,
                    accessLevel, isStatic, isConst);
   }

   private void parseClassConstant(String  accessLevel,
                                   boolean isStatic) throws TokenException
   {
      parseConstList(true, accessLevel, isStatic);
      if ( tokIs( Operators.SEMI_COLUMN ) )
      {
         nextToken();
      }
//      meta.clear();
//      modifiers.clear();
   }

   private void parseConstList(boolean isClassLevel,
                               String  accessLevel,
                               boolean isStatic) throws TokenException
   {
      consume( KeyWords.CONST );
//      convertMeta( meta );
//      convertModifiers( modifiers );
      collectVarListContent(isClassLevel, accessLevel, true, isStatic);
   }

   private void parseInterface() throws TokenException
   {
      consume( KeyWords.INTERFACE );
      _classInfo.setIsInterface();
      _classInfo.setClassName(tok.getText());
      nextToken(); // name
//      convertMeta( meta );
//      convertModifiers( modifier );

      if ( tokIs( KeyWords.EXTENDS ) )
      {
         nextToken(); // extends
         _classInfo.setExtends(getQualifiedType(parseQualifiedName()));
      }
      while ( tokIs( Operators.COMMA ) )
      {
         nextToken(); // comma
         parseQualifiedName();
      }
      consume( Operators.LEFT_CURLY_BRACKET );
      parseInterfaceContent();
      consume( Operators.RIGHT_CURLY_BRACKET );
   }

   private String parseQualifiedName() throws TokenException
   {
      final StringBuffer buffer = new StringBuffer();

      buffer.append( tok.getText() );
      nextToken();
      while ( tokIs( Operators.DOT )
            || tokIs( Operators.DOUBLE_COLUMN ) )
      {
         buffer.append( tok.getText() );
         nextToken();
         buffer.append( tok.getText() );
         nextToken(); // name
      }
      return buffer.toString();
   }

   private void parseFunctionSignature(String accessLevel,
                                       Boolean isStatic,
                                       Boolean isConst) throws TokenException
   {
//      final Node[] signature = doParseSignature();
      doParseSignature(true,
                       accessLevel,
                       isStatic,
                       isConst);
      skip( Operators.SEMI_COLUMN );
//      findFunctionTypeFromSignature();

//      final Node result = Node.create( findFunctionTypeFromSignature( signature ),
//                                       tok.getLine(),
//                                       tok.getColumn(),
//                                       signature[ 0 ].getStringValue() );
//      result.addChild( signature[ 1 ] );
//      result.addChild( signature[ 2 ] );
//      result.addChild( signature[ 3 ] );
//      return result;
   }

   private void doParseSignature(boolean isClassLevel,
                                 String accessLevel,
                                 Boolean isStatic,
                                 Boolean isConst) throws TokenException
   {
      consume( KeyWords.FUNCTION );

      if ( tokIs( KeyWords.SET )
            || tokIs( KeyWords.GET ) )
      {
         nextToken(); // set or get
      }

      String methodName = tok.getText();
      TransferOperation method = new TransferOperation(methodName, accessLevel);
      method.setStatic(isStatic);
      
      nextToken(); // name
      parseParameterList(method);
      method.setResultType(getQualifiedType(parseOptionalType()));
      method.setAnnotation(lastAnnotation);

      _classInfo.addMethod(method);
   }

   private void parseParameterList(TransferOperation method) throws TokenException
   {
      consume( Operators.LEFT_PARENTHESIS );

      while ( !tokIs( Operators.RIGHT_PARENTHESIS ) )
      {
         TransferParameter param = parseParameter();
         if (param != null)
            method.addParameter(param);
         if ( tokIs( Operators.COMMA ) )
         {
            nextToken();
         }
         else
         {
            break;
         }
      }
      consume( Operators.RIGHT_PARENTHESIS );
   }

   private String parseOptionalType() throws TokenException
   {
      String type = null;
      if ( tokIs( Operators.COLUMN ) )
      {
         nextToken();
         type = tok.getText();
         nextToken();
      }
      return type;
   }

   private void parseOptionalInit() throws TokenException
   {
      if ( tokIs( Operators.EQUAL ) )
      {
         nextToken();
         parseExpression();
      }
   }

   private TransferParameter parseParameter() throws TokenException
   {
      TransferParameter param = null;
      if ( tokIs( Operators.REST_PARAMETERS ) )
      {
         // variable length argument list
         nextToken(); // ...

         nextToken(); // rest
      }
      else
      {
         param = parseNameTypeInit();
      }
      return param;
   }

   private TransferAttribute parseNameTypeInit() throws TokenException
   {
      String varName = tok.getText();
      nextToken(); // name
      String varType = parseOptionalType();
      String qualifiedType = getQualifiedType(varType);
      TransferAttribute retField = new TransferAttribute(varName, qualifiedType);
      if (isCustomType(varType))
      {
         _classInfo.addDependency(qualifiedType);
      }

      if (qualifiedType.equals("Array"))
      {
         String metaType = extractTypeFromMeta(lastMeta);
         if (metaType == null)
            metaType = "Object";

         retField.setMetaType(getQualifiedType(metaType));
      }

      //_classInfo.addProperty(fileName, varName, parseOptionalType());
      parseOptionalInit();
      return retField;
   }

   final void parseInterfaceContent() throws TokenException
   {
      Boolean isStatic = false;
      Boolean isConst = false;

      while ( !tokIs( Operators.RIGHT_CURLY_BRACKET ) )
      {
         if ( tokIs( KeyWords.IMPORT ) )
         {
            parseImport();
         }
         else if ( tokIs( KeyWords.FUNCTION ) )
         {
            parseFunctionSignature("public", isStatic, isConst);
            isStatic = false;
            isConst = false;
            lastAnnotation = null;
         }
         else if ( tokIs( KeyWords.INCLUDE ) )
         {
            parseIncludeExpression();
         }
         else if ( tokIs( KeyWords.STATIC ) )
         {
            isStatic = true;
         }
         else if ( tokIs( KeyWords.CONST ) )
         {
            isConst = true;
         }
         else if ( tokIs( Operators.LEFT_SQUARE_BRACKET ) )
         {
            while ( !tokIs( Operators.RIGHT_SQUARE_BRACKET ) )
            {
               nextToken();
            }
            nextToken();
         }
         else
         {
            //nextToken();
            tryToParseCommentNode();
         }
      }
      
   }

   private void parseIncludeExpression() throws TokenException
   {
      consume( KeyWords.INCLUDE );
      parseExpression();

   }
   final void parseExpression() throws TokenException
   {
      parseAssignmentExpression();
   }

   private void parseAssignmentExpression() throws TokenException
   {
      parseConditionalExpression();
      while ( tokIs( Operators.EQUAL )
            || tokIs( Operators.PLUS_EQUAL ) || tokIs( Operators.MINUS_EQUAL )
            || tokIs( Operators.TIMES_EQUAL ) || tokIs( Operators.DIVIDED_EQUAL )
            || tokIs( Operators.MODULO_EQUAL ) || tokIs( Operators.AND_EQUAL ) || tokIs( Operators.OR_EQUAL )
            || tokIs( Operators.XOR_EQUAL ) )
      {
         nextToken();
         parseExpression();
      }
   }

   private void parseConditionalExpression() throws TokenException
   {
      parseOrExpression();
      if ( tokIs( Operators.QUESTION_MARK ) )
      {
         nextToken(); // ?
         parseExpression();
         nextToken(); // :
         parseExpression();
      }
   }

   private void parseOrExpression() throws TokenException
   {
      parseAndExpression();
      while ( tokIs( Operators.LOGICAL_OR ) )
      {
         nextToken();
         parseAndExpression();
      }
   }

   private void parseAndExpression() throws TokenException
   {
      parseBitwiseOrExpression();
      while ( tokIs( Operators.AND ) )
      {
         nextToken();
         parseBitwiseOrExpression();
      }
   }

   private void parseBitwiseOrExpression() throws TokenException
   {
      parseBitwiseXorExpression();
      while ( tokIs( Operators.B_OR ) )
      {
         nextToken();
         parseBitwiseXorExpression();
      }
   }

   private void parseBitwiseXorExpression() throws TokenException
   {
      parseBitwiseAndExpression();
      while ( tokIs( Operators.B_XOR ) )
      {
         nextToken();
         parseBitwiseAndExpression();
      }
   }

   private void parseBitwiseAndExpression() throws TokenException
   {
      parseEqualityExpression();
      while ( tokIs( Operators.B_AND ) )
      {
         nextToken();
         parseEqualityExpression();
      }
   }

   private void parseEqualityExpression() throws TokenException
   {
      parseRelationalExpression();
      while ( tokIs( Operators.DOUBLE_EQUAL )
            || tokIs( Operators.STRICTLY_EQUAL ) || tokIs( Operators.NON_EQUAL )
            || tokIs( Operators.NON_STRICTLY_EQUAL ) )
      {
         nextToken();
         parseRelationalExpression();
      }
   }

   private void parseRelationalExpression() throws TokenException
   {
      parseShiftExpression();
      while ( tokIs( Operators.INFERIOR )
            || tokIs( Operators.INFERIOR_OR_EQUAL ) || tokIs( Operators.SUPERIOR )
            || tokIs( Operators.SUPERIOR_OR_EQUAL ) || tokIs( KeyWords.IS )
            || tokIs( KeyWords.IN )
            && !isInFor || tokIs( KeyWords.AS ) || tokIs( KeyWords.INSTANCE_OF ) )
      {
         nextToken();
         parseShiftExpression();
      }
   }

   private void parseShiftExpression() throws TokenException
   {
      parseAdditiveExpression();
      while ( tokIs( Operators.DOUBLE_SHIFT_LEFT )
            || tokIs( Operators.TRIPLE_SHIFT_LEFT ) || tokIs( Operators.DOUBLE_SHIFT_RIGHT )
            || tokIs( Operators.TRIPLE_SHIFT_RIGHT ) )
      {

         nextToken();
         parseAdditiveExpression();
      }
   }

   private void parseAdditiveExpression() throws TokenException
   {
      parseMultiplicativeExpression();
      while ( tokIs( Operators.PLUS )
            || tokIs( Operators.MINUS ) )
      {
         nextToken();
         parseMultiplicativeExpression();
      }
   }

   private void parseMultiplicativeExpression() throws TokenException
   {
      parseUnaryExpression();
      while ( tokIs( Operators.TIMES )
            || tokIs( Operators.SLASH ) || tokIs( Operators.MODULO ) )
      {
         nextToken();
         parseUnaryExpression();
      }
   }

   final void parsePrimaryExpression() throws TokenException
   {

      if ( tokIs( Operators.LEFT_SQUARE_BRACKET ) )
      {
         parseArrayLiteral();
      }
      else if ( tokIs( Operators.LEFT_CURLY_BRACKET ) )
      {
         parseObjectLiteral();
      }
      else if ( tokIs( KeyWords.FUNCTION ) )
      {
         parseLambdaExpression();
      }
      else if ( tokIs( KeyWords.NEW ) )
      {
         parseNewExpression();
      }
      else if ( tokIs( Operators.LEFT_PARENTHESIS ) )
      {
         parseEncapsulatedExpression();
      }
      // else if ( tok.isNum()
      // || tokIs( KeyWords.TRUE ) || tokIs( KeyWords.FALSE ) || tokIs(
      // KeyWords.NULL )
      // || tok.getText().startsWith( Operators.DOUBLE_QUOTE.toString() )
      // || tok.getText().startsWith( Operators.SIMPLE_QUOTE.toString() )
      // || tok.getText().startsWith( Operators.SLASH.toString() )
      // || tok.getText().startsWith( Operators.INFERIOR.toString() ) || tokIs(
      // KeyWords.UNDEFINED ) )
      // {
      // nextToken();
      // }
      else
      {
         nextToken();
      }

   }

   private void parseNewExpression() throws TokenException
   {
      consume( KeyWords.NEW );

      String className = tok.getText();
      String newQualifiedName = getQualifiedType(className);
      if (!newQualifiedName.equals(getQualifiedType(_classInfo.getClassName())) &&
           isCustomType(newQualifiedName))
      {
         _classInfo.addDependency(newQualifiedName);
      }
      parseExpression(); // name
      if ( tokIs( Operators.LEFT_PARENTHESIS ) )
      {
         parseArgumentList();
      }
   }

   private void parseLambdaExpression() throws TokenException
   {
      TransferOperation method = new TransferOperation("", "");
      consume( KeyWords.FUNCTION );

      if ( tok.getText().compareTo( "(" ) != 0 )
      {
         nextToken();
      }
      parseParameterList(method);
      parseOptionalType();
      parseBlock();
   }

   private void parseObjectLiteral() throws TokenException
   {
      consume( Operators.LEFT_CURLY_BRACKET );
      while ( !tokIs( Operators.RIGHT_CURLY_BRACKET ) )
      {
         parseObjectLiteralPropertyDeclaration();
         skip( Operators.COMMA );
      }
      consume( Operators.RIGHT_CURLY_BRACKET );
   }

   private void parseObjectLiteralPropertyDeclaration() throws TokenException
   {
      nextToken(); // name
      consume( Operators.COLUMN );
      parseExpression();
   }

   /**
    * tok is the first token of a statement
    *
    * @throws TokenException
    */
   final void parseStatement() throws TokenException
   {
      if ( tokIs( KeyWords.FOR ) )
      {
         parseFor();
      }
      else if ( tokIs( KeyWords.IF ) )
      {
         parseIf();
      }
      else if ( tokIs( KeyWords.SWITCH ) )
      {
         parseSwitch();
      }
      else if ( tokIs( KeyWords.DO ) )
      {
         parseDo();
      }
      else if ( tokIs( KeyWords.WHILE ) )
      {
         parseWhile();
      }
      else if ( tokIs( KeyWords.TRY ) )
      {
         parseTry();
      }
      else if ( tokIs( KeyWords.CATCH ) )
      {
         parseCatch();
      }
      else if ( tokIs( KeyWords.FINALLY ) )
      {
         parseFinally();
      }
      else if ( tokIs( Operators.LEFT_CURLY_BRACKET ) )
      {
         parseBlock();
      }
      else if ( tokIs( KeyWords.VAR ) )
      {
         parseVar();
      }
      else if ( tokIs( KeyWords.CONST ) )
      {
         parseConst();
      }
      else if ( tokIs( KeyWords.RETURN ) )
      {
         parseReturnStatement();
      }
      else if ( tokIs( Operators.SEMI_COLUMN ) )
      {
         parseEmptyStatement();
      }
      else
      {
         parseExpressionList();
         skip( Operators.SEMI_COLUMN );
      }
   }

   private void parseTry() throws TokenException
   {
      nextToken();
      parseBlock();
   }

   private void parseCatch() throws TokenException
   {
      consume( KeyWords.CATCH );
      consume( Operators.LEFT_PARENTHESIS );
      nextToken(); // name
      if ( tokIs( Operators.COLUMN ) )
      {
         nextToken(); // :
         nextToken(); // type
      }
      consume( Operators.RIGHT_PARENTHESIS );
      parseBlock();
   }

   private void parseWhile() throws TokenException
   {
      consume( KeyWords.WHILE );
      parseCondition();
      parseStatement();
   }

   private void parseDo() throws TokenException
   {
      consume( KeyWords.DO );
      parseStatement();
      consume( KeyWords.WHILE );
      parseCondition();
      if ( tokIs( Operators.SEMI_COLUMN ) )
      {
         nextToken();
      }
   }

   private void parseSwitch() throws TokenException
   {
      consume( KeyWords.SWITCH );
      parseCondition();
      if ( tokIs( Operators.LEFT_CURLY_BRACKET ) )
      {
         nextToken();
         parseSwitchCases();
         consume( Operators.RIGHT_CURLY_BRACKET );
      }
   }

   private void parseSwitchCases() throws TokenException
   {
      for ( ;; )
      {
         if ( tokIs( Operators.RIGHT_CURLY_BRACKET ) )
         {
            break;
         }
         else if ( tokIs( KeyWords.CASE ) )
         {
            nextToken(); // case
            parseExpression();
            consume( Operators.COLUMN );
            parseSwitchBlock();
         }
         else if ( tokIs( KeyWords.DEFAULT ) )
         {
            nextToken(); // default
            consume( Operators.COLUMN );
            parseSwitchBlock();
         }
      }
   }

   private void parseSwitchBlock() throws TokenException
   {
      while ( !tokIs( KeyWords.CASE )
            && !tokIs( KeyWords.DEFAULT ) && !tokIs( Operators.RIGHT_CURLY_BRACKET ) )
      {
         parseStatement();
      }
   }

   private void parseIf() throws TokenException
   {
      consume( KeyWords.IF );
      parseCondition();
      parseStatement();
      if ( tokIs( KeyWords.ELSE ) )
      {
         nextToken();
         parseStatement();
      }
   }

   private void parseCondition() throws TokenException
   {
      consume( Operators.LEFT_PARENTHESIS );
      parseExpression();
      consume( Operators.RIGHT_PARENTHESIS );
   }

   private void parseFinally() throws TokenException
   {
      nextToken();
      parseBlock();
   }

   private void parseVar() throws TokenException
   {
      parseVarList(false, "private", false);
      skip( Operators.SEMI_COLUMN );
   }

   private void parseConst() throws TokenException
   {
      parseConstList(false, "private", false);
      skip( Operators.SEMI_COLUMN );
   }

   private void parseReturnStatement() throws TokenException
   {
      nextTokenAllowNewLine();
      if ( tokIs( NEW_LINE )
            || tokIs( Operators.SEMI_COLUMN ) )
      {
         nextToken();
      }
      else
      {
         parseExpression();
         skip( Operators.SEMI_COLUMN );
      }
   }

   private void parseEmptyStatement() throws TokenException
   {
      nextToken();
   }

   private void parseFor() throws TokenException
   {
      consume( KeyWords.FOR );

      if ( tokIs( KeyWords.EACH ) )
      {
         nextToken();
         parseForEach();
      }
      else
      {
         parseTraditionalFor();
      }
   }

   private void parseTraditionalFor() throws TokenException
   {
      consume( Operators.LEFT_PARENTHESIS );

      if ( !tokIs( Operators.SEMI_COLUMN ) )
      {
         if ( tokIs( KeyWords.VAR ) )
         {
            parseVarList(false, "private", false);
         }
         else
         {
            isInFor = true;
            parseExpression();
            isInFor = false;
         }
         if ( tokIs( NodeKind.IN.toString() ) )
         {
            parseForIn();
            return;
         }
      }
      consume( Operators.SEMI_COLUMN );
      if ( !tokIs( Operators.SEMI_COLUMN ) )
      {
         parseExpression();
      }
      consume( Operators.SEMI_COLUMN );
      if ( !tokIs( Operators.RIGHT_PARENTHESIS ) )
      {
         parseExpressionList();
      }
      consume( Operators.RIGHT_PARENTHESIS );
      parseStatement();
   }

   private void parseExpressionList() throws TokenException
   {
      parseAssignmentExpression();
      while ( tokIs( Operators.COMMA ) )
      {
         nextToken();
         parseAssignmentExpression();
      }
   }

   private void parseForIn() throws TokenException
   {
      nextToken();
      parseExpression();
   }

   private void parseForEach() throws TokenException
   {
      consume( Operators.LEFT_PARENTHESIS );

      if ( tokIs( KeyWords.VAR ) )
      {
         nextToken();
         parseNameTypeInit();
      }
      else
      {
         // names allowed?
         nextToken();
      }
      nextToken(); // in
      parseExpression();
      consume( Operators.RIGHT_PARENTHESIS );
      parseStatement();
   }

   private void parseEncapsulatedExpression() throws TokenException
   {
      consume( Operators.LEFT_PARENTHESIS );
      parseExpression();
      consume( Operators.RIGHT_PARENTHESIS );
   }

   final void parseUnaryExpression() throws TokenException
   {

      if ( tokIs( Operators.INCREMENT ) )
      {
         nextToken();
         parseUnaryExpression();
      }
      else if ( tokIs( Operators.DECREMENT ) )
      {
         nextToken();
         parseUnaryExpression();
      }
      else if ( tokIs( Operators.MINUS ) )
      {
         nextToken();
         parseUnaryExpression();
      }
      else if ( tokIs( Operators.PLUS ) )
      {
         nextToken();
         parseUnaryExpression();
      }
      else
      {
         parseUnaryExpressionNotPlusMinus();
      }

   }

   private void parseUnaryExpressionNotPlusMinus() throws TokenException
   {
      if ( tokIs( KeyWords.DELETE ) )
      {
         nextToken();
         parseExpression();
      }
      else if ( tokIs( KeyWords.VOID ) )
      {
         nextToken();
         parseExpression();
      }
      else if ( tokIs( KeyWords.TYPEOF ) )
      {
         nextToken();
         parseExpression();
      }
      else if ( tokIs( "!" ) )
      {
         nextToken();
         parseExpression();
      }
      else if ( tokIs( "~" ) )
      {
         nextToken();
         parseExpression();
      }
      else
      {
         parseUnaryPostfixExpression();
      }
   }

   private void parseUnaryPostfixExpression() throws TokenException
   {
      parsePrimaryExpression();

      if ( tokIs( Operators.LEFT_SQUARE_BRACKET ) )
      {
         parseArrayAccessor( );
      }
      else if ( tokIs( Operators.LEFT_PARENTHESIS ) )
      {
         parseFunctionCall(  );
      }
      if ( tokIs( Operators.INCREMENT ) )
      {
         parseIncrement(  );
      }
      else if ( tokIs( Operators.DECREMENT ) )
      {
         parseDecrement(  );
      }
      else if ( tokIs( Operators.DOT )
            || tokIs( Operators.DOUBLE_COLUMN ) )
      {
         parseDot(  );
      }
   }

   private void parseArrayAccessor() throws TokenException
   {
      while ( tokIs( Operators.LEFT_SQUARE_BRACKET ) )
      {
         nextToken(); // [
         parseExpression();
         consume( Operators.RIGHT_SQUARE_BRACKET );
      }
   }

   private void parseIncrement() throws TokenException
   {
      nextToken();
   }

   private void parseDecrement() throws TokenException
   {
      nextToken();
   }

   private void parseDot() throws TokenException
   {
      nextToken();
      if ( tokIs( Operators.LEFT_PARENTHESIS ) )
      {
         nextToken();
         parseExpression();
         consume( Operators.RIGHT_PARENTHESIS );
         return;
      }
      else if ( tokIs( Operators.TIMES ) )
      {
         return;
      }
      // else if ( tokIs( Operators.AT ) )
      // {
      // return parseE4XAttributeIdentifier();
      // }
      parseExpression();
   }

   /**
    * tok is function
    *
    * @param modifiers
    * @param meta
    * @throws TokenException
    */
   private void parseFunction(boolean isClassLevel,
                              String accessLevel,
                              boolean isStatic,
                              boolean isConst) throws TokenException
   {
      doParseSignature(isClassLevel,
                       accessLevel,
                       isStatic,
                       isConst);
//      findFunctionTypeFromSignature();

//      convertMeta();
//      convertModifiers();
      parseBlock();
   }

   private void parseBlock() throws TokenException
   {
      consume( Operators.LEFT_CURLY_BRACKET );

      while ( !tokIs( Operators.RIGHT_CURLY_BRACKET ) )
      {
         if ( tok.getText().startsWith( MULTIPLE_LINES_COMMENT ) )
         {
            nextToken();
         }
         else
         {
            parseStatement();
         }
      }
      consume( Operators.RIGHT_CURLY_BRACKET );
   }

   private void parseFunctionCall() throws TokenException
   {
      while ( tokIs( Operators.LEFT_PARENTHESIS ) )
      {
         parseArgumentList();
      }
      while ( tokIs( Operators.LEFT_SQUARE_BRACKET ) )
      {
         parseArrayLiteral();
      }
   }

   private void parseArrayLiteral() throws TokenException
   {
      consume( Operators.LEFT_SQUARE_BRACKET );
      while ( !tokIs( Operators.RIGHT_SQUARE_BRACKET ) )
      {
         parseExpression();
         skip( Operators.COMMA );
      }
      consume( Operators.RIGHT_SQUARE_BRACKET );
   }

   private void parseArgumentList() throws TokenException
   {
      consume( Operators.LEFT_PARENTHESIS );
      while ( !tokIs( Operators.RIGHT_PARENTHESIS ) )
      {
         parseExpression();
         skip( Operators.COMMA );
      }
      consume( Operators.RIGHT_PARENTHESIS );
   }

   private void skip( final Operators operator ) throws TokenException
   {
      skip( operator.toString() );
   }

   /**
    * Skip the current token, if it equals to the parameter
    *
    * @param text
    * @throws UnExpectedTokenException
    */
   private void skip( final String text ) throws TokenException
   {
      if ( tokIs( text ) )
      {
         nextToken();
      }
   }
   
   /**
    * Get the next token Skip comments and newlines for now In the end we want
    * to keep them though.
    *
    * @throws TokenException
    */
   final void nextToken() throws TokenException
   {
      do
      {
         nextTokenAllowNewLine();
      }
      while ( tok.getText().equals( NEW_LINE ) );
   }

   private void nextTokenAllowNewLine() throws TokenException
   {
      do
      {
         tok = scn.nextToken();
         /*
          * try { throw new Exception(); } catch( Exception e) {
          * StackTraceElement[] st = e.getStackTrace(); StackTraceElement ste =
          * st[ 1 ]; System.out.println( ste.getMethodName() + ":" +
          * tok.getText() ); }
          */
         if ( tok == null )
         {
            throw new NullTokenException( fileName );

         }
         if ( tok.getText() == null )
         {
            throw new NullTokenException( fileName );
         }
      }
      while ( tok.getText().startsWith( SINGLE_LINE_COMMENT ) );
   }

   private void nextTokenIgnoringDocumentation() throws TokenException
   {
      do
      {
         nextToken();
      }
      while ( tok.getText().startsWith( MULTIPLE_LINES_COMMENT ) );
   }

   private void tryToParseCommentNode() throws TokenException
   {
      if ( tok.getText().startsWith( ASDOC_COMMENT ) )
      {
         lastAnnotation = tok.getText();
         nextToken();
      }
      else if ( tok.getText().startsWith( MULTIPLE_LINES_COMMENT ) )
      {
         nextToken();
      }
      else
      {
         nextTokenIgnoringDocumentation();
      }
   }

}
